package com.yixing.tech.wms.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.convert.ConvertUtils;
import com.yixing.tech.common.enums.YesOrNo;
import com.yixing.tech.common.entity.*;
import com.yixing.tech.common.service.AbstractWmsService;
import com.yixing.tech.wms.entity.InStockWork;
import com.yixing.tech.wms.param.InventoryOrderSaveParam;
import com.yixing.tech.wms.service.*;
import com.yixing.tech.wms.stock.out.StockOutConstants;
import com.yixing.tech.wms.stock.out.callback.error.ErrorCodeEnum;
import com.yixing.tech.wms.stock.out.callback.error.TaskErrorCallbackVO;
import com.yixing.tech.wms.stock.out.domain.Task;
import com.yixing.tech.wms.stock.out.domain.vo.TaskMethod;
import com.yixing.tech.wms.stock.out.domain.vo.TaskType;
import com.yixing.tech.wms.utils.DocumentUtils;
import com.yixing.tech.wms.utils.P1Utils;
import com.yixing.tech.common.vo.s3.UserVO;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.ConvertOperators;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.yixing.tech.common.CommonConstant.*;

/**
 * @author yixing tech
 * @since 1.0.0
 */
@Slf4j
@Service
public class InventoryServiceImpl extends AbstractWmsService implements InventoryService {

    @Autowired
    private StockService stockService;
    @Autowired
    private WmsTaskService wmsTaskService;
    @Autowired
    private StockLocationService stockLocationService;
    @Autowired
    private ErpReportService erpReportService;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(InventoryOrderSaveParam param) throws BusinessException {
        String range = param.getRange();
        String materialCode = param.getMaterialCode();
        String locationCode = param.getLocationCode();
        String batch = param.getBatchNo();
        UserVO apiUser = getApiUser();

        if (StringUtils.isBlank(materialCode)) {
            throw new BusinessException("物料必选");
        }
        Document material = simpleFindOne("entity_" + MATERIAL, "code", materialCode);
        if (material == null) {
            throw new BusinessException(String.format("物料[%s]不存在", materialCode));
        }
        Document rule = simpleFindOne("entity_" + STOCK_RULE, "materialCode", materialCode);
        if (rule == null) {
            throw new BusinessException(String.format("物料[%s]的存储规则不存在", materialCode));
        }
        List<String> areas = rule.getList("areas", String.class);

        List<Document> stocks;
        if (InventoryRange.material.getCode().equals(range)) {
            // 查询所有满足的库存信息
            stocks = stockService.queryStocks(materialCode, areas, null, batch);
        } else if (InventoryRange.location.getCode().equals(range)) {
            if (StringUtils.isBlank(locationCode)) {
                throw new BusinessException("库位必选");
            }
            Document location = simpleFindOne("entity_" + STOCK_LOCATION, "code", locationCode);
            if (location == null) {
                throw new BusinessException(String.format("库位[%s]不存在", locationCode));
            }
            stocks = stockService.queryStocks(materialCode, areas, locationCode, batch);
        } else {
            throw new BusinessException("非法的盘点范围");
        }

        if (CollectionUtils.isEmpty(stocks)) {
            throw new BusinessException("无满足条件的库存供盘点");
        }

        // 保存盘点单
        InventoryOrder inventoryOrder = new InventoryOrder();
        inventoryOrder.setCode(new ObjectId().toString());
        inventoryOrder.setRange(newDocument("id", new ObjectId(range)));
        inventoryOrder.setMaterialCode(materialCode);
        inventoryOrder.setLocationCode(locationCode);
        inventoryOrder.setBatchNo(batch);
        inventoryOrder.setTenantId(tenantId);
        inventoryOrder.setCreateTime(new Date());
        inventoryOrder.setCreateId(apiUser.createId);
        inventoryOrder.setCreateName(apiUser.createName);
        inventoryOrder.setMaterialNum(1D);

        inventoryOrder.setErpWareHouse(param.getErpWareHouse());
        inventoryOrder.setErpCostCenterOrgUnit(param.getErpCostCenterOrgUnit());
        inventoryOrder.setErpStorageOrgUnit(param.getErpStorageOrgUnit());
        inventoryOrder.setErpAdminOrgUnit(param.getErpAdminOrgUnit());
        inventoryOrder.setErpCostItem(param.getErpCostItem());

        Set<String> locationCodes = new HashSet<>();

        // 创建盘点作业
        List<InventoryWork> inventoryWorks = new LinkedList<>();
        // 按照 容器 、 物料 、 批次 、 隔板 、 颜色 、 语种 分组
        Map<String, List<Document>> stockMapList = new LinkedHashMap<>();
        for (Document stock : stocks) {
            locationCodes.add(stock.getString("locationCode"));
            String containerCode = stock.getString("containerCode");
            String matCode = stock.getString("materialCode");
            String batchNo = stock.getString("batchNo");
            String partitionSide = stock.getString("partitionSide");
            String colorName = stock.getString("colorName");
            String languageName = stock.getString("languageName");
            String key = containerCode + ":" + matCode + ":" + batchNo + ":" + partitionSide + ":" + colorName + ":" + languageName;
            stockMapList.computeIfAbsent(key, k -> new ArrayList<>()).add(stock);
        }

        inventoryOrder.setLocationNum(locationCodes.size());
        inventoryOrder = mongoTemplate.save(inventoryOrder, "entity_" + INVENTORY_ORDER);

        // 相同的库位与容器为一组
        Map<String, String> groupMap = new HashMap<>();

        Map<String, Document> containerMap = new HashMap<>();

        for (List<Document> ss : stockMapList.values()) {
            Document s = ss.get(0);
            String lc = s.getString("locationCode");
            String bt = s.getString("batchNo");
            String cc = s.getString("containerCode");
            String cr = s.getString("colorName");
            String la = s.getString("languageName");
            String ps = s.getString("partitionSide");

            // 查询容器
            Document container = containerMap.get(cc);
            if (container == null) {
                container = simpleFindOne("entity_" + CONTAINER, "code", cc);
                if (container == null) {
                    throw new BusinessException(String.format("容器[%s]不存在", cc));
                }
                containerMap.put(cc, container);
            }

            String groupKey = lc + ":" + cc;
            String group = groupMap.computeIfAbsent(groupKey, x -> new ObjectId().toString());

            double checkNum = 0;
            for (Document doc : ss) {
                checkNum += P1Utils.safeGetDouble(doc, "realNum", 0D);
            }

            InventoryWork inventoryWork = new InventoryWork();
            inventoryWork.setCode(new ObjectId().toString());
            inventoryWork.setInventoryCode(inventoryOrder.getCode());
            inventoryWork.setMaterialCode(materialCode);
            inventoryWork.setMaterialName(material.getString("materialName"));
            inventoryWork.setLocationCode(lc);
            inventoryWork.setBatchNo(bt);
            inventoryWork.setPartitionSide(ps);
            inventoryWork.setContainerCode(cc);
            inventoryWork.setContainerType(newDocument("id", new ObjectId(container.getEmbedded(Arrays.asList("type", "id"), String.class))));
            inventoryWork.setColorName(cr);
            inventoryWork.setLanguageName(la);
            inventoryWork.setCheckNum(checkNum);
            inventoryWork.setStocks(ss);
            inventoryWork.setGroup(group);

            inventoryWork.setTenantId(tenantId);
            inventoryWork.setCreateTime(new Date());
            inventoryWork.setCreateId(apiUser.createId);
            inventoryWork.setCreateName(apiUser.createName);

            inventoryWorks.add(inventoryWork);
        }

        mongoTemplate.insert(inventoryWorks, "entity_" + INVENTORY_WORK);
        return true;
    }

    /**
     * 下架
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean out(String code) throws BusinessException {
        // 查找对应的 盘点作业
        List<InventoryWork> works = mongoTemplate.find(Query.query(newCriteria().and("inventoryCode").is(code)),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        if (CollectionUtils.isEmpty(works)) {
            throw new BusinessException("没有对应的盘点作业");
        }
        // 查询物料AB分类
        String materialCode = works.get(0).getMaterialCode();
        Document material = simpleFindOne("entity_" + MATERIAL, "code", materialCode);
        if (material == null) {
            throw new BusinessException(String.format("物料[%s]不存在", materialCode));
        }
        String abc = material.getString("abc");

        InventorySet inventorySet = getSet();
        String endArea;
        if (A.equals(abc)) {
            endArea = inventorySet.getPickAreaA();
        } else if (B.equals(abc)) {
            // endArea = inventorySet.getPickAreaB();
            // B类物料CTU任务下架任务需要固定指定终点库区为  B类原材料拣货入口库区
            endArea = AreaCodeDict.bMatPickingEntrance.getText();
        } else {
            throw new BusinessException("非法的物料ABC分类");
        }

        // 盘点作业按照 库位与容器进行分组
        Map<String, List<InventoryWork>> workMapList = new LinkedHashMap<>();
        works.forEach(x -> {
            String key = x.getLocationCode() + ":" + x.getContainerCode();
            workMapList.computeIfAbsent(key, k -> new ArrayList<>()).add(x);
        });

        List<Task> tasks = new LinkedList<>();
        workMapList.values().forEach(x -> {
            String containerCode = x.get(0).getContainerCode();
            String locationCode = x.get(0).getLocationCode();
            List<String> codes = x.stream().map(xx -> xx.getCode()).collect(Collectors.toList());
            // 创建下架任务
            Task task = Task.builder()
                    .taskCode(UUID.randomUUID().toString().replaceAll("-", ""))
                    .taskType(TaskType.STOCK_OUT.getId())
                    .startLoc(locationCode)
                    .endArea(endArea)
                    .podCode(containerCode)
                    .business(INVENTORY_BIZ)
                    .operationNo(codes)
                    .build();
            tasks.add(task);

        });
        for (Task task : tasks) {
            wmsTaskService.saveTaskInfo(task);
        }
        return true;
    }

    /**
     * 上报单个作业
     *
     * @param workCode
     */
    @Override
    public boolean reportWork(String workCode) throws BusinessException {
        // 查找对应的 盘点作业
        InventoryWork work = mongoTemplate.findOne(Query.query(newCriteria().and("code").is(workCode)),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        if (work == null) {
            throw new BusinessException("没有对应的盘点作业");
        }
        String inventoryCode = work.getInventoryCode();

        // 校验状态
        String erpBillId = work.getErpBillId();
        if (StringUtils.isNoneBlank(erpBillId)) {
            throw new BusinessException("已经完成Erp上报，不能重复上报");
        }

        String workStatus = DocumentUtils.unwrapDictItem(work.getStatus());
        if (!InventoryStatus.finished.getCode().equals(workStatus)) {
            throw new BusinessException("当前盘点作业未返仓，不能上报");
        }

        // 查询盘点单据
        InventoryOrder inventoryOrder = mongoTemplate.findOne(Query.query(newCriteria().and("code").is(inventoryCode)),
                InventoryOrder.class, "entity_" + INVENTORY_ORDER);
        if (inventoryOrder == null) {
            throw new BusinessException("没有对应的盘点单据");
        }

        double diff = work.getRealNum() - work.getCheckNum();
        if (diff == 0) {
            throw new BusinessException("无需上报");
        }

        String soapMethod;
        String param;
        String returnQName;
        ErpReturn erpReturn;
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
        if (diff > 0) {
            ErpInventoryGain gain = new ErpInventoryGain();
            gain.setBizType("501"); // erp 业务类型：盘盈入库
            gain.setTransactionType("032"); // erp 事务类型：其他入库
            gain.setCostCenterOrgUnit(inventoryOrder.getErpCostCenterOrgUnit());
            gain.setStorageOrgUnit(inventoryOrder.getErpStorageOrgUnit());
            gain.setAdminOrgUnit(inventoryOrder.getErpAdminOrgUnit());

            ErpInventoryGainEntry gainEntry = new ErpInventoryGainEntry();
            gainEntry.setMaterial(work.getMaterialCode());
            gainEntry.setQty(diff + "");
            gainEntry.setWarehouse(inventoryOrder.getErpWareHouse());
            gainEntry.setCostObject(work.getMaterialCode());
            gainEntry.setCostItem(inventoryOrder.getErpCostItem());
            gain.setEntrys(Arrays.asList(gainEntry));

            soapMethod = "CreateCheckIn";
            returnQName = "CreateCheckInReturn";
            try {
                param = mapper.writeValueAsString(gain);
            } catch (JsonProcessingException e) {
                throw new BusinessException(e);
            }
        } else {
            ErpInventoryLoss loss = new ErpInventoryLoss();
            loss.setBizType("511"); // erp 业务类型：盘亏出库
            loss.setTransactionType("029"); // erp 事务类型：其他出库
            loss.setCostCenterOrgUnit(inventoryOrder.getErpCostCenterOrgUnit());
            loss.setStorageOrgUnit(inventoryOrder.getErpStorageOrgUnit());
            loss.setAdminOrgUnit(inventoryOrder.getErpAdminOrgUnit());

            ErpInventoryLossEntry lossEntry = new ErpInventoryLossEntry();
            lossEntry.setMaterial(work.getMaterialCode());
            lossEntry.setQty(diff + "");
            lossEntry.setWarehouse(inventoryOrder.getErpWareHouse());
            lossEntry.setCostObject(work.getMaterialCode());
            loss.setEntrys(Arrays.asList(lossEntry));

            soapMethod = "CreateCheckOut";
            returnQName = "CreateCheckOutReturn";
            try {
                param = mapper.writeValueAsString(loss);
            } catch (JsonProcessingException e) {
                throw new BusinessException(e);
            }
        }

        // 上报
        String result = erpReportService.erpReport(soapMethod, param, returnQName);
        ObjectMapper mapper2 = new ObjectMapper();
        try {
            Map map = mapper2.readValue(new ByteArrayInputStream(result.getBytes()), Map.class);
            erpReturn = mapper2.convertValue(map, ErpReturn.class);
        } catch (IOException e) {
            throw new BusinessException(e);
        }

        // 更新单据
        Update update = new Update();
        int status = erpReturn.getStatus();
        if (status == 0) {
            update.set("erpReport", true);
        } else {
            update.set("erpReport", false);
        }
        update.set("erpAudit", erpReturn.getStatus() == 0);
        update.set("erpMsg", erpReturn.getMsg());
        update.set("erpReportTime", new Date());
        update.set("erpBizDate", ConvertUtils.convertIfPossible(erpReturn.getBillDate(), Date.class));
        update.set("erpBillNumber", erpReturn.getBillNumber());
        update.set("erpBillId", erpReturn.getBillId());
        updateById(work.get_id().toString(), update, "entity_" + INVENTORY_WORK);

        return true;
    }

    /**
     * 完成盘点执行回仓
     *
     * @param workCode
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean finishBack(String workCode) throws BusinessException {
        // 查询盘点作业
        InventoryWork work = mongoTemplate.findOne(Query.query(newCriteria().and("code").is(workCode)),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        if (work == null) {
            throw new BusinessException("盘点作业不存在");
        }

        // 查询同组的盘点作业
        String group = work.getGroup();
        List<InventoryWork> works = mongoTemplate.find(Query.query(newCriteria().and("group").is(group)),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        for (InventoryWork w : works) {
            // 校验状态
            String status = DocumentUtils.unwrapDictItem(w.getStatus());
            if (!InventoryStatus.checked.getCode().equals(status)) {
                throw new BusinessException("该容器存在未完成盘点的盘点作业，不能执行回仓");
            }
        }

        String containerCode = work.getContainerCode();
        Document startLocation = simpleFindOne("entity_" + STOCK_LOCATION, "containerCode", containerCode);
        if (startLocation == null) {
            throw new BusinessException(String.format("容器[%s]对应的开始地点不存在，不能执行回仓", containerCode));
        }

        String materialCode = work.getMaterialCode();
        Document rule = simpleFindOne("entity_" + STOCK_RULE, "materialCode", materialCode);
        if (rule == null) {
            throw new BusinessException("物料规则不存在");
        }
        Document material = simpleFindOne("entity_" + MATERIAL, "code", materialCode);
        if (material == null) {
            throw new BusinessException(String.format("物料[%s]不存在", materialCode));
        }

        String abc = material.getString("abc");

        // 申请库位
        List<Document> locations = stockLocationService.queryEmptyLocation(rule.getList("areas", String.class), false);
        if (CollectionUtils.isEmpty(locations)) {
            throw new BusinessException("申请库位失败");
        }
        assertBiz(() -> A.equals(abc) && locations.size() <= 4, "A库库位不足以分配");

        Document endLocation = locations.get(0);
        // 库位上锁
        Update update2 = new Update();
        update2.set("lock", true);
        update2.set("updateTime", new Date());
        if (!versionUpdateById(endLocation, update2, "entity_" + STOCK_LOCATION)) {
            throw new BusinessException("库位上锁失败，请重新尝试");
        }

        // 盘点作业更新
        List<String> workCodes = works.stream().map(InventoryWork::getCode).collect(Collectors.toList());
        Update update3 = new Update();
        update3.set("returnLocationCode", endLocation.getString("code"));
        mongoTemplate.updateMulti(Query.query(newCriteria().and("code").in(workCodes)), update3, "entity_" + INVENTORY_WORK);

        String startLocationCode;
        if (abc.equals(A)) {
            startLocationCode = startLocation.getString("code");
        } else if (abc.equals(B)) {
            // B类物料任务上架任务需要固定指定起点库位为   B类原材料拣货出口库区的第一个库位
            String startLocationArea = AreaCodeDict.bMatPickingExit.getText();
            Document locationDoc = simpleFindOne("entity_" + STOCK_LOCATION, "area", startLocationArea);
            if (locationDoc == null) {
                throw new BusinessException(String.format("库区[%s]下无库位", startLocationArea));
            }
            startLocationCode = locationDoc.getString("code");
        } else {
            throw new BusinessException("未知的物料ABC分类无法执行");
        }

        // 创建上架任务
        Task task = Task.builder()
                .taskCode(UUID.randomUUID().toString().replaceAll("-", ""))
                .taskType(TaskType.STOCK_IN.getId())
                .startLoc(startLocationCode)
                .endArea(endLocation.getString("area"))
                .endLoc(endLocation.getString("code"))
                .podCode(containerCode)
                .business(INVENTORY_BIZ)
                .operationNo(workCodes)
                .build();
        wmsTaskService.saveTaskInfo(task);

        return true;
    }

    @Override
    public Object detail(String containerCode) {
        // 查询盘点作业
        return mongoTemplate.findOne(
                Query.query(newCriteria().and("containerCode").is(containerCode)
                        .and("status").is(InventoryStatus.out.wrapDictItemDoc())),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
    }

    /**
     * 进行盘点
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean check(String workCode, double realNum, String inventoryNote) throws BusinessException {
        if (realNum <= 0) {
            throw new BusinessException("盘点数量应该大于0");
        }
        // 查询盘点作业
        InventoryWork work = mongoTemplate.findOne(Query.query(newCriteria().and("code").is(workCode)),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        if (work == null) {
            throw new BusinessException("盘点作业不存在");
        }
        String status = DocumentUtils.unwrapDictItem(work.getStatus());
        if (!status.equals(InventoryStatus.out.getCode())) {
            throw new BusinessException("盘点作业当前状态不能执行盘点确认");
        }
        UserVO apiUser = getApiUser();
        Update update = new Update();
        update.set("status", InventoryStatus.checked.wrapDictItemDoc());
        update.set("realNum", realNum);
        update.set("updateTime", new Date());
        update.set("inventoryTime", new Date());
        update.set("inventoryUserId", apiUser.getId());
        update.set("inventoryUserName", apiUser.getName());
        update.set("inventoryNote", inventoryNote);
        if (realNum == work.getRealNum()) {
            update.set("erpResFlag", true);
        }
        if (updateById(work.get_id().toString(), update, "entity_" + INVENTORY_WORK)) {
            // 查询是否还有作业
            if (detail(work.getContainerCode()) == null) {
                // 回仓
                return finishBack(workCode);
            } else {
                return true;
            }
        } else {
            throw new BusinessException("盘点确认失败");
        }
    }

    @Override
    public void callback(String taskCode, String method, String locCode, String podCode) {
        log.info("盘点处理任务回调信息:taskCode[{}],method[{}],locCode[{}],podCode[{}]", taskCode, method, locCode, podCode);

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("taskCode").is(taskCode)));
        operations.add(Aggregation.addFields()
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .addFieldWithValue("status", ConvertOperators.ToString.toString("$status.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .build());

        AggregationResults<Task> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), StockOutConstants.TASK_TEMPLATE, Task.class);
        if (ObjectUtils.isEmpty(results.getMappedResults())) {
            return;
        }
        Task task = results.getMappedResults().get(0);
        if (TaskMethod.OUTBIN.getAction().equals(method)) {
            // 处理离开起点
            handleOutBin(taskCode, method, locCode, podCode, task);
        } else if (TaskType.STOCK_OUT.getId().equals(task.getTaskType()) && TaskMethod.FINISH.getAction().equals(method)) {
            // 容器到达产线区域
            containerArrive(taskCode, method, locCode, podCode, task);
        } else if (TaskType.STOCK_IN.getId().equals(task.getTaskType()) && TaskMethod.FINISH.getAction().equals(method)) {
            // 容器到达仓库
            containerInStock(taskCode, method, locCode, podCode, task);
        }
    }

    @Override
    public Object errorCallback(TaskErrorCallbackVO errorCallbackVO) throws BusinessException {
        ErrorCodeEnum errorCodeEnum = ErrorCodeEnum.getByCode(errorCallbackVO.getErrorCode());
        if (ObjectUtils.isEmpty(errorCodeEnum)) {
            log.error("异常码不在预期范围内:errorCode[{}]", errorCallbackVO.getErrorCode());
            throw new BusinessException("异常码不在预期范围内");
        }

        Task task = wmsTaskService.findTaskByTaskCode(errorCallbackVO.getTaskCode());
        if (ObjectUtils.isEmpty(task)) {
            log.error("异常任务信息不存在:taskCode[{}]", errorCallbackVO.getTaskCode());
            throw new BusinessException("异常任务信息不存在");
        }

        Object result = handleCommonError(task);

//        switch (errorCodeEnum) {
//            case DOUBLE_IN:
//                result = doubleIn(task);
//                break;
//            case DOUBLE_IN2:
//                result = doubleIn2(task);
//                break;
//            case FETCHING_NOTHING:
//                result = fetchingNothing(task);
//                break;
//            case FETCHING_NOTHING2:
//                result = fetchingNothing2(task);
//        }
        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    public String handleCommonError(Task task) throws BusinessException {
        List<String> workCodes = task.getOperationNo();
        List<InventoryWork> works = mongoTemplate.find(Query.query(newCriteria().and("code").in(workCodes)),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        InventoryWork iw = works.get(0);
        String status = DocumentUtils.unwrapDictItem(iw.getStatus());
        if (status.equals(InventoryStatus.notOut.getCode())) {
            // 发生在分拣前取消任务
            return cancelWork(works);
        } else {
            // 发生在分拣后重新查找库位
            return reApplyLocation(works);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public String reApplyLocation(List<InventoryWork> works) throws BusinessException {
        InventoryWork iw = works.get(0);
        String materialCode = iw.getMaterialCode();
        Document rule = mongoTemplate.findOne(Query.query(newCriteria().and("materialCode").is(materialCode)), Document.class,
                "entity_" + STOCK_RULE);
        Document material = simpleFindOne("entity_" + MATERIAL, "code", materialCode);
        String abc = material.getString("abc");
        List<String> areas = rule.getList("areas", String.class);
        List<Document> locations = stockLocationService.queryEmptyLocation(areas, false);
        if (CollectionUtils.isEmpty(locations)) {
            return cancelWork(works);
        } else if (A.equals(abc) && locations.size() <= 5) {
            return cancelWork(works);
        } else {
            Document fl = locations.get(0);
            String locationCode = fl.getString("code");
            Update update = new Update();
            update.set("returnLocationCode", locationCode);
            update.set("updateTime", new Date());
            works.forEach(x -> updateById(x.get_id().toString(), update, "entity_" + INVENTORY_WORK));
            return locationCode;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public String cancelWork(List<InventoryWork> works) {
        Update delete = new Update();
        delete.set("isDelete", 1);
        works.forEach(x -> updateById(x.get_id().toString(), delete, "entity_" + INVENTORY_WORK));
        return "NULL";
    }

    public boolean erpInventoryCallBack(ErpInventoryResult result) {
        String logId = new ObjectId().toString();
        log.info("erp盘点回调[{}]: 结果[{}]", logId, result);
        String erpBillId = result.getBillId();

        // 查询盘点作业s
        InventoryWork work = mongoTemplate.findOne(Query.query(newCriteria().and("erpBillId").is(erpBillId)),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        log.info("erp盘点回调[{}]: 盘点作业: [{}]", logId, work);

        if (work == null) {
            log.error("盘点回调作业不存在, erpBillId: {}", erpBillId);
            return false;
        }

        if (work.isErpResFlag()) {
            log.error("盘点作业已经审核完毕，不能重复审核, erpBillId: {}", erpBillId);
            return false;
        }

        // 更新
        Update update = new Update();
        update.set("erpResFlag", true);
        update.set("erpApproved", result.isApproved());
        update.set("erpReason", result.getReason());
        updateById(work.get_id().toString(), update, "entity_" + INVENTORY_WORK);

        // 查询同组的盘点作业，如果都成功得到回复就更新库存、释放资源
        List<InventoryWork> works = mongoTemplate.find(Query.query(newCriteria().and("group").is(work.getGroup())),
                InventoryWork.class, "entity_" + INVENTORY_WORK);
        boolean erpResFlag = true;
        for (InventoryWork w : works) {
            if (!w.isErpResFlag()) {
                erpResFlag = false;
                break;
            }
        }

        List<StockFlow> flows = new LinkedList<>();
        if (erpResFlag) {
            for (InventoryWork w : works) {
                if (!w.isErpApproved()) {
                    continue;
                }
                // 生成流水
                double diff = w.getRealNum() - w.getCheckNum();
                if (diff == 0) {
                    continue;
                }

                // 更新库存
                // 盘点作业关键： 容器 、 物料 、 批次 、 隔板 、 颜色 、 语种
                Criteria criteria = newCriteria().and("containerCode").is(w.getContainerCode())
                        .and("materialCode").is(w.getMaterialCode())
                        .and("partitionSide").is(w.getPartitionSide())
                        .and("colorName").is(w.getColorName())
                        .and("languageName").is(w.getLanguageName());
                if (StringUtils.isNoneBlank(w.getBatchNo())) {
                    criteria = criteria.and("batchNo").is(w.getBatchNo());
                }
                Query query = Query.query(criteria);
                List<Document> stocks = mongoTemplate.find(query, Document.class, "entity_" + STOCK_NAME);
                log.info("erp回调wms更新库存: {}", stocks);

                if (diff > 0) {
                    // 更新第一条数据
                    Document doc = stocks.get(0);
                    Update updateAdd = new Update();
                    updateAdd.set("realNum", w.getRealNum() + P1Utils.safeGetDouble(doc, "realNum", 0D));
                    updateById(doc, update, "entity_" + STOCK_NAME);
                } else {
                    // 依次扣减
                    double deleteNum = Math.abs(diff);
                    for (Document stock : stocks) {
                        if (deleteNum <= 0) {
                            break;
                        }
                        double realNum =  P1Utils.safeGetDouble(stock, "realNum", 0D);
                        if (realNum <= deleteNum) {
                            deleteNum -= realNum;
                            Update delete = new Update();
                            delete.set("isDelete", 1);
                            updateById(stock, delete, "entity_" + STOCK_NAME);
                        } else if (realNum > deleteNum) {
                            deleteNum = 0;
                            Update update1 = new Update();
                            update1.set("realNum", realNum - deleteNum);
                            update1.set("updateTime", new Date());
                            updateById(stock, update1, "entity_" + STOCK_NAME);
                        }
                    }
                }

                // 创建盘点流水
                StockFlow flow = new StockFlow();
                flow.setOrder(w.getInventoryNote());
                flow.setTaskCode(w.getCode());
                flow.setTaskObjName("盘点作业");
                flow.setFlowBizType(diff > 0 ? FlowBizType.inventoryGain.wrapDictItemDoc() : FlowBizType.inventoryLoss.wrapDictItemDoc());
                flow.setLocationCode(w.getLocationCode());
                flow.setContainerCode(w.getContainerCode());
                flow.setMaterialCode(w.getMaterialCode());
                flow.setPartitionSide(w.getPartitionSide());
                flow.setBatchNo(w.getBatchNo());
                flow.setNum(Math.abs(diff));
                flow.setCreateTime(new Date());
                flow.setTenantId(tenantId);
                flows.add(flow);
            }
            if (!flows.isEmpty()) {
                mongoTemplate.insert(flows, "entity_" + STOCK_FLOW);
            }

            // 释放资源
            unlockContainer(work.getContainerCode());
            unlockLocation(work.getLocationCode());
        }

        return true;
    }

    /**
     * 盘盈申请单审核结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean erpInventoryGain(ErpInventoryResult result) {
        return erpInventoryCallBack(result);
    }

    /**
     * 盘亏申请单审核结果
     */
    @Override
    public boolean erpInventoryLoss(ErpInventoryResult result) {
        return erpInventoryCallBack(result);
    }

    @Transactional(rollbackFor = Exception.class)
    public void containerInStock(String taskCode, String method, String locCode, String podCode, Task task) {
        List<String> operationNo = task.getOperationNo();
        if (!CollectionUtils.isEmpty(operationNo)) {
            Update update = new Update();
            update.set("status", InventoryStatus.finished.wrapDictItemDoc());
            update.set("updateTime", new Date());
            mongoTemplate.updateMulti((Query.query(newCriteria().and("code").in(operationNo))),
                    update, "entity_" + INVENTORY_WORK);
        }

        // ！！！容器 与 库位 保持锁定，等待erp回复结果再释放锁！！！

        // 库位与容器绑定并解锁库位
        Update update3 = new Update();
        update3.set("lock", true);
        update3.set("containerCode", podCode);
        update3.set("updateTime", new Date());
        mongoTemplate.updateFirst((Query.query(newCriteria().and("code").is(locCode))), update3, "entity_" + STOCK_LOCATION);

        // 锁容器
        Update update4 = new Update();
        update4.set("lock", true);
        update4.set("updateTime", new Date());
        mongoTemplate.updateFirst((Query.query(newCriteria().and("code").is(podCode))), update4, "entity_" + CONTAINER);

        // 库存更新
        Update update2 = new Update();
        update2.set("locationCode", locCode);
        update2.set("areaCode", task.getEndArea());
        update2.set("updateTime", new Date());
        UpdateResult updateResult2 = mongoTemplate.updateMulti(Query.query(newCriteria().and("containerCode").is(podCode)), update2,
                "entity_" + STOCK_NAME);
        log.info("盘点任务到达仓库库位节点回调,更新库存库位[{}]绑定容器[{}],更新结果[{}]", locCode, update2, updateResult2);
    }

    @Transactional(rollbackFor = Exception.class)
    public void containerArrive(String taskCode, String method, String locCode, String podCode, Task task) {
        List<String> operationNo = task.getOperationNo();
        String abc = null;
        if (!CollectionUtils.isEmpty(operationNo)) {
            Update update = new Update();
            update.set("status", InventoryStatus.out.wrapDictItemDoc());
            update.set("endLocation", locCode);
            update.set("updateTime", new Date());
            mongoTemplate.updateMulti((Query.query(newCriteria().and("code").in(operationNo))),
                    update, "entity_" + INVENTORY_WORK);

            // 下架到达分拣台入口的时候，不需要绑定库位与料箱
            List<InStockWork> inStockTasks = mongoTemplate.find(Query.query(newCriteria().and("code").in(task.getOperationNo())),
                    InStockWork.class, "entity_" + IN_STOCK_TASK);
            if(!CollectionUtils.isEmpty(inStockTasks)) {
                abc = inStockTasks.get(0).getAbc();
            }
        }

        // 解锁容器
        unlockContainer(podCode);
        // 容器到达
        Update update = new Update();
        if(A.equals(abc)) {
            update.set("containerCode", podCode);
        }
        update.set("lock", false);
        UpdateResult updateResult = mongoTemplate.updateFirst(Query.query(newCriteria().and("code").is(locCode)),
                update, "entity_" + STOCK_LOCATION);

        log.info("盘点任务离开起点到达产线区,更新库位[{}]绑定的容器[{}],更新结果[{}]", locCode, update, updateResult);
    }

    private void unlockContainer(String containerCode) {
        Update update2 = new Update();
        update2.set("lock", false);
        update2.set("updateTime", new Date());
        mongoTemplate.updateFirst((Query.query(newCriteria().and("code").is(containerCode))), update2, "entity_" + CONTAINER);
    }

    private void unlockLocation(String locationCode) {
        Update update2 = new Update();
        update2.set("lock", false);
        update2.set("updateTime", new Date());
        mongoTemplate.updateFirst((Query.query(newCriteria().and("code").is(locationCode))), update2, "entity_" + STOCK_LOCATION);
    }

    @Transactional(rollbackFor = Exception.class)
    public void handleOutBin(String taskCode, String method, String locCode, String podCode, Task task) {
        List<String> operationNo = task.getOperationNo();
        if (!CollectionUtils.isEmpty(operationNo)) {
            Update update = new Update();
            update.set("status", InventoryStatus.moving.wrapDictItemDoc());
            update.set("updateTime", new Date());
            mongoTemplate.updateMulti((Query.query(newCriteria().and("code").in(operationNo))),
                    update, "entity_" + INVENTORY_WORK);
        }

        // 处理离开起点
        // 起点库位解绑容器，并解锁
        Update update = new Update();
        update.unset("containerCode");
        update.set("lock", false);
        UpdateResult updateResult = mongoTemplate.updateFirst(Query.query(newCriteria().and("code").is(locCode)), update,
                "entity_" + STOCK_LOCATION);
        log.info("盘点任务离开起点节点回调,更新库位[{}]绑定的容器[{}],更新结果[{}]", locCode, update, updateResult);

        // 库存更新：移除库位与库区，保留容器码
        Update update2 = new Update();
        update2.unset("locationCode");
        update2.unset("areaCode");
        UpdateResult updateResult2 = mongoTemplate.updateMulti(Query.query(newCriteria().and("locationCode").is(locCode)), update2,
                "entity_" + STOCK_NAME);
        log.info("盘点任务离开起点节点回调,更新库存[{}]解绑容器[{}],更新结果[{}]", locCode, update, updateResult2);
    }

    private InventorySet getSet() throws BusinessException {
        // 查询配置
        InventorySet set = mongoTemplate.findOne(Query.query(newCriteria()), InventorySet.class, "entity_" + INVENTORY_SET);
        if (set == null) {
            throw new BusinessException("盘点配置不存在");
        }
        if (StringUtils.isAnyBlank(set.getPickAreaA(), set.getPickAreaB())) {
            throw new BusinessException("盘点配置无效，请检查");
        }
        return set;
    }
}
